// ============================================================================
// FileName: MainConsole.cs
//
// Description:
// Main console display for the demonstration SIP Proxy.
//
// Author(s):
// Aaron Clauson
//
// History:
// 13 Aug 2006	Aaron Clauson	Created.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using BlueFace.Net.STUN;
using BlueFace.Sys;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Instrumentation;
using BlueFace.VoIP.Net;
using BlueFace.VoIP.Net.SIP;
using BlueFace.VoIP.SIPServer;
using BlueFace.VoIP.SIPServerCores.StatefulProxy;
using BlueFace.VoIP.SIPServerCores.StatefulProxy.DialPlanApps;
using log4net;

#if WINDOWS
using System.ServiceModel;
#endif

namespace BlueFace.VoIP.SIP.StatefulProxy
{

#if CONSOLE

    class MainConsole
    {
       private static ManualResetEvent m_proxyUp = new ManualResetEvent(false);

        // Enable if the SIP Proxy should be run as a console and not a Windows Service daemon.
       [STAThread]
        static int Main(string[] args)
        {
            try
            {
                Console.WriteLine("sipswitch starting");

                StatefulDaemon daemon = new StatefulDaemon();
					
                Thread daemonThread = new Thread(new ThreadStart(daemon.Start));
                daemonThread.Start();
				
                m_proxyUp.WaitOne();	
                return 2;
            }
            catch(Exception excp)
            {
                Console.WriteLine("Exception Main. " + excp.Message);
                return 0;
            }
        }
    }

#endif
	
    /// <summary>
    /// Storage hooks:
    /// Registrar:
    ///  - LoadSIPDomains
    ///  - m_sipSwitchRegistrarPersistor.LoadPersistedRegistrarRecord
    ///  - m_sipSwitchRegistrarPersistor.PersistRegistrationRecord
    /// Registration Agent:
    ///  - Load3rdPartyRegistrations
    /// Application Server:
    ///  - GetSIPAccountBindings
    ///  - DoesSIPAccountExist
    ///  - SIPCDR_CDRUpdated
    ///  - LoadUserDialPlan
    ///  - GetExtensionOwner
    ///  - GetSIPAccountOwner
    ///  - LoadSIPDomains
    ///  - LoadSIPAccountFromDB
    ///  - m_dbLogger.AddEvent
    /// Monitor:
    ///  - proxyMonitor_ProxyAuthenticate
    ///  Proxy Server:
    ///  - None.
    /// </summary>
	public class StatefulDaemon
	{
        public const string VERSION = "0.2.1";
        public const int CHECK_REGEXPIRY_DURATION = 2000;               // Period at which to check for expired registrations.
        public const int MINIMUM_RELOAD_DELAY = 15;                     // Minimum period between reloads of the registration agent.   
        private const string PROXY_RUNTIMECONFIG_TEMPEXTEN = ".tmp";
        private const string SIP_REGISTRARSERVER_STRING = "www.mysipswitch.com";
        private const int DIALPLAN_NOTUSED_PURGEPERIOD = 300;           // The number of seconds after last use that a dialplan will be purged for.
        
        private static ILog logger = AssemblyStateProxy.logger;

        private SIPTransport m_sipProxyTransport;
        private SIPTransactionEngine m_transactionEngine;
        private SIPTransport m_registrarSIPTransport;
        private SIPTransport m_regAgentSIPTransport;
        private SIPTransport m_appServerSIPTransport;

		//private XmlNode m_localSIPSocketsNode = AssemblyStateProxy.LocalSIPSocketsNode;
        //private XmlNode m_registrarRealmsNode = AssemblyStateProxy.RegistrarRealmsNode;
        private XmlNode m_sipAppServerConfigNode = AssemblyStateProxy.SIPAppServerConfigNode;
        private XmlNode m_sipStatelssProxyConfigNode = AssemblyStateProxy.SIPStatelessProxyConfigNode;
        private XmlNode m_sipRegistrarConfigNode = AssemblyStateProxy.SIPRegistrarConfigNode;

        private static string m_runtimeConfigFile = AssemblyStateProxy.RuntimeConfigFile;
		private static IPEndPoint m_monitorEndPoint = AssemblyStateProxy.MonitorListenSocket;
        private static int m_monitorEventPort = AssemblyStateProxy.MonitorEventPort;
        private static bool m_registrarEnabled = AssemblyStateProxy.RegistrarEnabled;
        private static bool m_proxyEnabled = AssemblyStateProxy.ProxyEnabled;
        private static bool m_registrationAgentEnabled = AssemblyStateProxy.RegistrationAgentEnabled;
        private static StorageTypes m_proxyDBStorageType = AssemblyStateProxy.ProxyDBStorageType;
        private static string m_proxyConnStr = AssemblyStateProxy.ProxyDBConnStr;
        private static bool m_logAll = AssemblyStateProxy.LogAll;
        private static bool m_manglePrivate = AssemblyStateProxy.MangleClientContacts;
        private static StorageTypes m_proxyLogDBStorageType = AssemblyStateProxy.ProxyLogStorageType;
        private static string m_proxyLogDBConnStr = AssemblyStateProxy.ProxyLogDBConnStr; 
        private static int m_registrarContactsPerUser = AssemblyStateProxy.RegistrarContactsPerUser;
        //private static XmlNode m_registrarExpirysNode = AssemblyStateProxy.RegistrarExpirysNode;
        //private static bool m_registrarUseDownstreamProxy = AssemblyStateProxy.RegistrarUseDownstreamProxy;
        private static string m_registrarSocket = AssemblyStateProxy.RegistrarSocket;
        private static string m_registrationAgentSocket = AssemblyStateProxy.RegistrationAgentSocket;
        private static string m_registrationAgentProxySocket = AssemblyStateProxy.RegistrationAgentProxySocket;
        private IPEndPoint m_secondarySTUNEndPoint = AssemblyStateProxy.STUNSecondaryEndPoint;
        private IPEndPoint m_natKeepAliveEndPoint = AssemblyStateProxy.NATKeepAliveListenerEndPoint;
        private int m_natKeepAliveSendInterval = AssemblyStateProxy.NATKeepAliveSendInterval;
        private string m_superUsername = AssemblyStateProxy.SuperUsername;
        private string m_traceDirectory = AssemblyStateProxy.TraceDirectory;
        private string m_betamaxServersPattern = AssemblyStateProxy.BetamaxServersPattern;
        private bool m_webServiceEnabled = AssemblyStateProxy.WebServiceEnabled;
        private string m_currentDirectory = AssemblyStateProxy.CurrentDirectory;
        private string m_rubyScriptCommonPath = AssemblyStateProxy.RubyScriptCommonPath;
        private bool m_useSIPTransportMetrics = AssemblyStateProxy.UseSIPTransportMetrics;
        private string m_metricsFileName = AssemblyStateProxy.MetricsFileName;
        private string m_metricsFileCopyName = AssemblyStateProxy.MetricsFileCopyName;
        private string m_metricsLocalGraphDir = AssemblyStateProxy.MetricsLocalGraphDir;
        private int m_metricsGraphPeriod = AssemblyStateProxy.MetricsGraphPeriod;
        private bool m_appServerEnabled = AssemblyStateProxy.AppServerEnabled;
        //private string m_appServerSocket = AssemblyStateProxy.AppServerSocket;
        private string m_proxyScriptPath = AssemblyStateProxy.ProxyScriptPath;
        private IPEndPoint m_outboundProxySocket = AssemblyStateProxy.OutboundProxySocket;

        private DatabaseLogger m_dbLogger;
        //private StorageLayer m_sipSwitchStorageLayer;
        private SIPSwitchPersistor m_sipSwitchPersistor;
        private NATKeepAliveRelay m_natKeepAliveRelay;
        private UdpClient m_natKeepAliveSender;
        private STUNListener m_secondarySTUNListener;
        private STUNServer m_stunServer;
        private IPEndPoint m_primaryProxyIPEndPoint;
        private SIPRegistrationAgent m_regAgent;
        private SIPRegistrations m_registrationsStore;
        private RegistrarCore m_registrar;
        private SIPSwitchRegistrarPersistor m_sipSwitchRegistrarPersistor;
        private SIPRegistrationAgentPersistor m_sipRegistrationAgentPersistor;
        private ProxyMonitorChannel m_monitorChannel;
        private ProxyMonitor m_proxyMonitor;
        private string m_rubyScriptCommon;
        private SIPTransportMetricsGraphAgent m_metricsGraphAgent;
        private SIPDomains m_sipDomains;
        private StatefulProxyCore m_statefulProxy;

        private bool m_stop = false;    // Set to true to stop the object monitor thread.

#if WINDOWS
        private ServiceHost m_hService;
#endif

        private Dictionary<string, SIPDialPlan> m_userDialPlans = new Dictionary<string, SIPDialPlan>();

		public StatefulDaemon()
		{}

        public void Start()
        {
            try
            {
                logger.Debug("version=" + VERSION + ".");
                logger.Debug("pid=" + Process.GetCurrentProcess().Id + ".");
                logger.Debug("os=" + System.Environment.OSVersion + ".");
                logger.Debug("current directory=" + m_currentDirectory + ".");

                // Send events from this process to the monitoring socket.
                if (m_monitorEventPort != 0)
                {
                    // Events will be sent by the monitor channel to the loopback interface and this port.
                    m_monitorChannel = new ProxyMonitorChannel(m_monitorEventPort);
                }

                // The storagelayer will be utilised by any SIP server agent that requires a database (which is all of them except the monitor).
                if (m_appServerEnabled || m_registrarEnabled || m_registrationAgentEnabled || m_monitorEndPoint != null)
                {
                    //m_sipSwitchStorageLayer = new StorageLayer(m_proxyDBStorageType, m_proxyConnStr);
                    m_sipSwitchPersistor = SIPSwitchPersistorFactory.CreateSIPSwitchPersistor(m_proxyDBStorageType, m_proxyConnStr);

                    // These events are only fired by the XML persistor but it won't matter if they are wired up for all since the others won't fire the events.
                    m_sipSwitchPersistor.DomainsUpdated += new SIPDomainsUpdatedDelegate(DomainsUpdated);
                    m_sipSwitchPersistor.SIProvidersUpdated += new SIPProvidersUpdatedDelegate(SIProvidersUpdated);
                    m_sipSwitchPersistor.DialplansUpdated += new SIPDialPlansUpdatedDelegate(DialplansUpdated);
                }

                #region Initialise the Stateless Proxy Core.

                if (m_proxyEnabled)
                {
                    logger.Debug("Initiating SIP Stateless Proxy Agent.");

                    XmlNode proxySIPSocketsNode = m_sipStatelssProxyConfigNode.SelectSingleNode("sipsockets");

                    if (proxySIPSocketsNode == null || proxySIPSocketsNode.ChildNodes.Count == 0)
                    {
                        //throw new ApplicationException("Empty IP Address for proxy cannot start.");
                        logger.Warn("No IP addresses specified for SIP Proxy, the SIP Proxy will be disabled.");
                    }
                    else
                    {
                        #region Initialise the SIPTransport layer.

                        m_sipProxyTransport = new SIPTransport(null, false, m_useSIPTransportMetrics);
                        CreateSocketsForSIPTransport(m_sipProxyTransport, proxySIPSocketsNode);

                        m_sipProxyTransport.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(LogSIPProxyRequestIn);
                        m_sipProxyTransport.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(LogSIPProxyRequestOut);
                        m_sipProxyTransport.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(LogSIPProxyResponseIn);
                        m_sipProxyTransport.SIPResponseOutTraceEvent += new SIPTransportSIPResponseOutTraceDelegate(LogSIPProxyResponseOut);
                        m_sipProxyTransport.SIPBadRequestInTraceEvent += new SIPTransportSIPBadRequestInTraceDelegate(LogSIPBadRequestIn);
                        m_sipProxyTransport.SIPBadResponseInTraceEvent += new SIPTransportSIPBadResponseInTraceDelegate(LogSIPBadResponseIn);
                        m_sipProxyTransport.UnrecognisedMessageReceived += new UnrecognisedMessageReceivedDelegate(UnrecognisedMessageReceived);

                        // If metrics are in use start the graphing thread.
                        //if (m_useSIPTransportMetrics)
                        //{
                        //    m_metricsGraphAgent = new SIPTransportMetricsGraphAgent(m_metricsLocalGraphDir, m_metricsFileName, m_metricsFileCopyName);
                        //    m_metricsGraphAgent.Start(m_metricsGraphPeriod);
                        //}

                        #endregion

                        StatelessProxyCore proxyCore = new StatelessProxyCore(
                            logEvent => StatefulProxyLogEvent(logEvent),
                             m_sipProxyTransport,
                            m_proxyScriptPath);

                        // Events that pass the SIP requests and responses onto the Stateless Proxy Core.
                        m_sipProxyTransport.SIPTransportRequestReceived += new SIPTransportRequestReceivedDelegate(proxyCore.GotRequest);
                        m_sipProxyTransport.SIPTransportResponseReceived += new SIPTransportResponseReceivedDelegate(proxyCore.GotResponse);

                        #region Initialise the NAT keep-alive multiplexer.

                        if (m_natKeepAliveEndPoint != null && m_sipProxyTransport != null)
                        {
                            m_natKeepAliveRelay = new NATKeepAliveRelay(m_sipProxyTransport, m_natKeepAliveEndPoint, StatefulProxyLogEvent);
                        }
                        else
                        {
                            logger.Warn("The Proxy's NAT keep-alive relay was not enabled as the socket to use was not specified.");
                        }

                        #endregion

                        #region Initialise the STUN server if required.

                        if (m_secondarySTUNEndPoint != null)
                        {
                            m_secondarySTUNListener = new STUNListener(m_secondarySTUNEndPoint);   // This end point is only for secondary STUN messages.
                            m_stunServer = new STUNServer(m_primaryProxyIPEndPoint, STUNSend, m_secondarySTUNEndPoint, m_secondarySTUNListener.Send);
                            m_sipProxyTransport.STUNRequestReceived += new STUNRequestReceivedDelegate(m_stunServer.STUNPrimaryReceived);
                            m_sipProxyTransport.STUNRequestReceived += new STUNRequestReceivedDelegate(LogPrimarySTUNRequestReceived);
                            m_secondarySTUNListener.MessageReceived += new STUNMessageReceived(m_stunServer.STUNSecondaryReceived);
                            m_secondarySTUNListener.MessageReceived += new STUNMessageReceived(LogSecondarySTUNRequestReceived);

                            logger.Debug("STUN server successfully initialised.");
                        }
                        else
                        {
                            logger.Warn("The STUN server was disabled.");
                        }

                        #endregion
                    }
                }

                #endregion

                #region Initialise the App Server (previously known as Stateful SIP Proxy Core) and its logging mechanisms including CDRs.

                if (m_appServerEnabled)
                {
                    logger.Debug("Initiating SIP Application Server Agent.");

                    XmlNode appServerSocketsNode = m_sipAppServerConfigNode.SelectSingleNode("sipsockets");

                    if (appServerSocketsNode == null || appServerSocketsNode.ChildNodes.Count == 0)
                    {
                        //throw new ApplicationException("Empty IP Address for proxy cannot start.");
                        logger.Warn("No IP addresses specified for App Server it will be disabled.");
                    }
                    else
                    {
                        #region Initialise the SIPTransport layer.

                        m_transactionEngine = new SIPTransactionEngine();
                        m_appServerSIPTransport = new SIPTransport(m_transactionEngine, true, m_useSIPTransportMetrics);
                        CreateSocketsForSIPTransport(m_appServerSIPTransport, appServerSocketsNode);

                        m_appServerSIPTransport.SIPRequestInTraceEvent += new SIPTransportSIPRequestInTraceDelegate(LogSIPRequestIn);
                        m_appServerSIPTransport.SIPRequestOutTraceEvent += new SIPTransportSIPRequestOutTraceDelegate(LogSIPRequestOut);
                        m_appServerSIPTransport.SIPResponseInTraceEvent += new SIPTransportSIPResponseInTraceDelegate(LogSIPResponseIn);
                        m_appServerSIPTransport.SIPResponseOutTraceEvent += new SIPTransportSIPResponseOutTraceDelegate(LogSIPResponseOut);
                        m_appServerSIPTransport.SIPBadRequestInTraceEvent += new SIPTransportSIPBadRequestInTraceDelegate(LogSIPBadRequestIn);
                        m_appServerSIPTransport.SIPBadResponseInTraceEvent += new SIPTransportSIPBadResponseInTraceDelegate(LogSIPBadResponseIn);
                        m_appServerSIPTransport.UnrecognisedMessageReceived += new UnrecognisedMessageReceivedDelegate(UnrecognisedMessageReceived);

                        // If metrics are in use start the graphing thread.
                        if (m_useSIPTransportMetrics)
                        {
                            m_metricsGraphAgent = new SIPTransportMetricsGraphAgent(m_metricsLocalGraphDir, m_metricsFileName, m_metricsFileCopyName);
                            m_metricsGraphAgent.Start(m_metricsGraphPeriod);
                        }

                        #endregion

                        if (m_sipDomains == null)
                        {
                            m_sipDomains = m_sipSwitchPersistor.LoadSIPDomains();
                        }

                        m_statefulProxy = new StatefulProxyCore(
                            m_appServerSIPTransport, 
                            (m_registrarSocket != null) ? IPSocket.ParseSocketString(m_registrarSocket) : null, 
                            (m_registrationAgentSocket != null) ? IPSocket.ParseSocketString(m_registrationAgentSocket) : null, 
                            m_manglePrivate, 
                            m_sipDomains,
                            LoadUserDialPlan,
                            LoadSIPAccount,
                            GetExtensionOwner,
                            GetSIPAccountBindings,
                            DoesSIPAccountExist,
                            StatefulProxyLogEvent);

                        if (m_proxyLogDBConnStr != null && m_proxyLogDBConnStr.Trim().Length != 0)
                        {
                            if (m_proxyLogDBStorageType == StorageTypes.Postgresql || m_proxyLogDBStorageType == StorageTypes.MSSQL)
                            {
                                m_dbLogger = new DatabaseLogger(m_proxyLogDBStorageType, m_proxyLogDBConnStr);
                                // Set up the CDR handler.
                                SIPCDR.CDRUpdated += new CDRUpdatedDelegate(SIPCDR_CDRUpdated);
                            }
                        }
                        else
                        {
                            logger.Warn("App Server logging to the database was not enabled as no connection string was specified.");
                        }

                       SIPCallResolver.BetamaxServersPattern = m_betamaxServersPattern;    // Allow the SIPCallDescriptor to identfy Betamax servers so that a special SIP header can be set.
                        SIPDialPlan.TraceDirectory = m_traceDirectory;                      // Set the directory on the server to store SIP traces in.
                    }
                }
                else
                {
                    logger.Warn("The App Server was disabled.");
                }

                #endregion

                #region Initialise the proxy telnet monitor if required.

                if (m_monitorEventPort != 0 && m_monitorEndPoint != null)
                {
                    logger.Debug("Initiating Monitor Agent.");

                    // Events will be sent by the monitor channel to the loopback interface and this port.
                    m_monitorChannel = new ProxyMonitorChannel(m_monitorEventPort);

                    // Listen for the monitor channel events and forward them onto clients.
                    m_proxyMonitor = new ProxyMonitor(new IPEndPoint[] { m_monitorEndPoint }, m_monitorEventPort, null, m_superUsername);
                    m_proxyMonitor.ProxyAuthenticate += new ProxyAuthenticateDelegate(proxyMonitor_ProxyAuthenticate);
                    m_proxyMonitor.StartMonitoring();

                    logger.Debug("The proxy console monitor was successfully started on " + m_monitorEndPoint + ".");
                }
                else
                {
                    logger.Debug("Proxy monitor disabled.");
                }

                #endregion

                #region Initialise the SIP Registrar Core.

                if (m_registrarEnabled)
                {
                    logger.Debug("Initialising SIP Registrar Agent.");

                    if (m_sipDomains == null)
                    {
                        m_sipDomains = m_sipSwitchPersistor.LoadSIPDomains();
                    }

                    LoadRegistrarRecordDelegate loadRegistrarRecord = null;
                    PersistRegistrarRecordDelegate persistRegistrarRecord = null;
                    if (m_proxyDBStorageType == StorageTypes.Postgresql)
                    {
                        m_sipSwitchRegistrarPersistor = new SIPSwitchRegistrarPersistor(m_proxyDBStorageType, m_proxyConnStr);
                        m_sipSwitchRegistrarPersistor.StatefulProxyLogEvent += new ProxyLogDelegate(StatefulProxyLogEvent);
                        m_sipSwitchRegistrarPersistor.StartPersistorThreads(1);
                        loadRegistrarRecord = m_sipSwitchRegistrarPersistor.LoadPersistedRegistrarRecord;
                        persistRegistrarRecord = m_sipSwitchRegistrarPersistor.PersistRegistrationRecord;
                    }
                    else
                    {
                        loadRegistrarRecord = m_sipSwitchPersistor.LoadPersistedRegistrarRecord;
                    }

                    m_registrationsStore = new SIPRegistrations(m_sipDomains, StatefulProxyLogEvent, loadRegistrarRecord, persistRegistrarRecord, SendNATKeepAlive);
                    m_registrationsStore.StartCheckContacts();

                    SIPTransactionEngine registrarTransactionEngine = new SIPTransactionEngine();
                    m_registrarSIPTransport = new SIPTransport(registrarTransactionEngine, true, m_useSIPTransportMetrics);
                    m_registrarSIPTransport.AddSIPChannel(IPSocket.ParseSocketString(m_registrarSocket));

                    Dictionary<string, SIPUserAgentConfiguration> userAgentExpirys = null;
                    if (m_sipRegistrarConfigNode != null)
                    {
                        userAgentExpirys = LoadUserAgentConfigs(m_sipRegistrarConfigNode.SelectSingleNode("useragents"));
                    }
                    else
                    {
                        logger.Debug("UserAgent config's for the registrar were not loaded as the SIP Registrar config node was missing.");
                    }

                    m_registrar = new RegistrarCore(m_registrarSIPTransport, m_registrationsStore, m_manglePrivate, true, SIP_REGISTRARSERVER_STRING, m_registrarContactsPerUser, StatefulProxyLogEvent, userAgentExpirys);
                    m_registrarSIPTransport.SIPTransportRequestReceived += new SIPTransportRequestReceivedDelegate(m_registrar.AddRegisterRequest);

                    if (m_natKeepAliveEndPoint != null)
                    {
                        try
                        {
                            m_natKeepAliveSender = new UdpClient();
                            logger.Debug("NATKeepAlives for SIP Registrar enabled, endpoint is " + m_natKeepAliveEndPoint + ".");
                        }
                        catch (Exception excp)
                        {
                            logger.Error("Exception creating NATKeepAlive sender. " + excp.Message);
                        }
                    }
                    else
                    {
                        logger.Warn("The Registrar's NAT keep-alive relay was not enabled as the socket to use was not specified.");
                    }

                    logger.Debug("SIP Registrar successfully initialised.");
                }
                else
                {
                    logger.Debug("The Registrar function was disabled.");
                }

                #endregion

                #region Initialise the 3rd party registrations agent.

                if (m_registrationAgentEnabled)
                {
                    logger.Debug("Initiating SIP Registration Agent.");

                    if (m_registrationAgentSocket != null)
                    {
                        SIPTransactionEngine regAgentTransactionEngine = new SIPTransactionEngine();
                        m_regAgentSIPTransport = new SIPTransport(regAgentTransactionEngine);
                        m_regAgentSIPTransport.AddSIPChannel(IPSocket.ParseSocketString(m_registrationAgentSocket));

                        List<UserRegistration> thirdPartyRegs = Load3rdPartyRegistrations(m_proxyDBStorageType, m_proxyConnStr);
                        m_regAgent = new SIPRegistrationAgent(m_regAgentSIPTransport, IPSocket.ParseSocketString(m_registrationAgentProxySocket), thirdPartyRegs);
                        m_regAgent.StatefulProxyLogEvent += new ProxyLogDelegate(StatefulProxyLogEvent);

                        if (m_proxyDBStorageType == StorageTypes.Postgresql)
                        {
                            m_sipRegistrationAgentPersistor = new SIPRegistrationAgentPersistor(m_proxyDBStorageType, m_proxyConnStr);
                            m_sipRegistrationAgentPersistor.StartPersistorThreads(1);
                            m_regAgent.RegistrationStateChanged += new RegistrationStateChangedDelegate(m_sipRegistrationAgentPersistor.RegistrationStateChanged);
                        }

                        m_regAgent.Start();
                        logger.Debug("Registration agent started.");
                    }
                    else
                    {
                        logger.Warn("Cannot start the Registration Agent as no socket has been specified for it.");
                    }
                }
                else
                {
                    logger.Debug("The RegistrationAgent was disabled.");
                }

                #endregion

#if WINDOWS
                if (m_webServiceEnabled)
                {
                    // If running on Windows the Sys.Http library can be used to allow the sipswitch to host web services. If Linux disable.
                    SIPProxyWebService proxyWebService = new SIPProxyWebService();
                    proxyWebService.SetSIPRegistrationAgent(m_regAgent);
                    proxyWebService.SetSIPTransport(m_appServerSIPTransport);
                    m_hService = new ServiceHost(proxyWebService);
                    m_hService.Open();
                    proxyWebService.StatefulProxyLogEvent += new ProxyLogDelegate(StatefulProxyLogEvent);
                    proxyWebService.RegistrationsStore = m_registrationsStore;
                    logger.Debug("SIP Proxy web service was successfully started on " + m_hService.BaseAddresses[0].AbsoluteUri + ".");
                }
                else
                {
                    logger.Debug("The web service was disabled.");
                }
#endif

                //ThreadPool.QueueUserWorkItem(new WaitCallback(MeasureObjectUsage));

                // Initialise random number to save delay on first SIP request.
                Crypto.GetRandomString();
            }
            catch (Exception excp)
            {
                logger.Error("Exception Start. " + excp.Message);
                throw excp;
            }
        }

        private void DialplansUpdated()
        {
            lock (m_userDialPlans)
            {
                m_userDialPlans.Clear();
            }
        }

        private void DomainsUpdated(SIPDomains sipDomains)
        {
            m_sipDomains = sipDomains;

            if (m_statefulProxy != null)
            {
                m_statefulProxy.ProxySIPDomains = sipDomains;
            }

            if (m_registrationsStore != null)
            {
                m_registrationsStore.RegistrarSIPDomains = sipDomains;
            }
        }

        private void SIProvidersUpdated()
        {
            lock (m_userDialPlans)
            {
                m_userDialPlans.Clear();
            }

            if (m_regAgent != null)
            {
                m_regAgent.Restart(m_sipSwitchPersistor.Load3rdPartyRegistrations());
            }
        }

        private void CreateSocketsForSIPTransport(SIPTransport sipTransport, XmlNode socketsNode)
        {
            foreach (XmlNode sipSocketNode in socketsNode.ChildNodes)
            {
                string localSocket = sipSocketNode.InnerText;
                SIPProtocolsEnum protocol = SIPProtocolsEnum.UDP;

                if (sipSocketNode.Attributes.GetNamedItem("protocol") != null)
                {
                    protocol = SIPProtocolsType.GetProtocolType(sipSocketNode.Attributes.GetNamedItem("protocol").Value);
                }

                if (m_primaryProxyIPEndPoint == null)
                {
                    m_primaryProxyIPEndPoint = IPSocket.ParseSocketString(localSocket);
                }

                if (protocol == SIPProtocolsEnum.UDP)
                {
                    SIPUDPChannel udpChannel = new SIPUDPChannel(IPSocket.ParseSocketString(localSocket));
                    sipTransport.AddSIPChannel(udpChannel);
                }
                else if (protocol == SIPProtocolsEnum.TCP)
                {
                    SIPTCPChannel tcpChannel = new SIPTCPChannel(IPSocket.ParseSocketString(localSocket));
                    sipTransport.AddSIPChannel(tcpChannel);
                }
                else
                {
                    logger.Warn("Could not create a SIP channel for protocol " + protocol + ".");
                }
            }
        }

        /// <summary>
        /// Looks up the registered contact for a SIP account.
        /// </summary>
        private List<SIPAddressBinding> GetSIPAccountBindings(string user, string domain)
        {
            try
            {
                if (user == null || user.Trim().Length == 0)
                {
                    StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "The user supplised to GetSIPAccountBindings was empty.", null));
                    return null;
                }
                else if (domain == null || domain.Trim().Length == 0)
                {
                    StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "The domain supplised to GetSIPAccountBindings was empty.", null));
                    return null;
                }

                string canonicalDomain = m_sipDomains.GetDomain(domain);

                if (canonicalDomain == null)
                {
                    StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "Domain " + domain + " was not recognised as belonging to this proxy.", null));
                    return null;
                }

                List<SIPAddressBinding> bindings = null;

                if (m_proxyDBStorageType == StorageTypes.Postgresql)
                {
                    bindings = m_sipSwitchPersistor.GetSIPAccountBindings(user, canonicalDomain);
                }
                else
                {
                    SIPRegistrarRecord registrarRecord = m_registrationsStore.Lookup(new SIPParameterlessURI(SIPSchemesEnum.sip, canonicalDomain, user));
                    if (registrarRecord != null)
                    {
                        bindings = registrarRecord.GetBindings();
                    }
                }
                
                if (bindings == null)
                {
                    StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "No SIP account match for " + user + "@" + domain + ".", null));
                }

                return bindings;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetSIPAccountBindings. " + excp.Message);
                return null;
            }
        }

        private bool DoesSIPAccountExist(SIPURI uri)
        {
            if (uri.User == null || uri.Host == null)
            {
                return false;
            }

            string canonicalDomain = m_sipDomains.GetDomain(uri.Host);

            if (canonicalDomain == null)
            {
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "Domain " + uri.Host + " was not recognised as belonging to this proxy.", null));
                return false;
            }
            else
            {
                return m_sipSwitchPersistor.DoesSIPAccountExist(uri.User, canonicalDomain);
            }
        }

        /// <summary>
        /// Write a log message measuring object usage by the application. The main purpose of this method is to detect any memory leaks
        /// and where they are.
        /// </summary>
        private void MeasureObjectUsage(object state)
        {
            try
            {
                while (!m_stop)
                {
                    GC.Collect();
                    //string monitorMessage =
                     //   "transactions p=" + SIPTransaction.Count + ", c=" + SIPTransaction.TransactionsCreated + ", d=" + SIPTransaction.TransactionsDestroyed + ", " +
                    //    "dialplans p=" + m_userDialPlans.Count + ", s=" + SIPDialPlan.ScriptCount + ", c=" + SIPDialPlan.Created + ", d=" + SIPDialPlan.Destroyed + ", " +
                    //    "switchcallm c=" + SwitchCallMulti.Created + ", d=" + SwitchCallMulti.Destroyed + ", " +
                    //    "switchcall c=" + SIPCallDescriptor.Created + ", d=" + SIPCallDescriptor.Destroyed + ".";
                    //StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Monitor, monitorMessage, null));

                    //string registrarMonitorMessage =
                    //    "registrations p=" + SIPRegistrations.Count + ", " +
                    //    "registrarrecords c=" + SIPRegistrarRecord.Created + ", d=" + SIPRegistrarRecord.Destroyed + ", " +
                    //    "addressbindings c=" + SIPAddressBinding.Created + ", d=" + SIPAddressBinding.Destroyed + "," +
                    //    "regagent p=" + SIPRegistrationAgent.Count + ", " +
                    //    "userreg c=" + UserRegistration.Created + ", d=" + UserRegistration.Destroyed + ".";
                    //StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Monitor, registrarMonitorMessage, null));

                    /*string resourcesString =
                        "sipaccounts c=" + SIPAccount.Created + ", d=" + SIPAccount.Destroyed + ", " +
                        "sipprovider c=" + SIPProvider.Created + ", d=" + SIPProvider.Destroyed + ", " +
                        "sipcdr c=" + SIPCDR.CreatedCount + ", d=" + SIPCDR.Destroyed + ", " +
                        "sipdialogue c=" + SIPDialogue.Created + ", d=" + SIPDialogue.Destroyed + ".";
                    StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Monitor, resourcesString, null));*/

                    // Check dialplans and remove any that are not being used.
                    /*List<SIPDialPlan> tobeRemoved = new List<SIPDialPlan>();
                    foreach (SIPDialPlan dialPlan in m_userDialPlans.Values)
                    {
                        if (DateTime.Now.Subtract(dialPlan.LastUsed).TotalSeconds > DIALPLAN_NOTUSED_PURGEPERIOD)
                        {
                            tobeRemoved.Add(dialPlan);
                        } 
                    }

                    if (tobeRemoved.Count > 0)
                    {
                        lock (m_userDialPlans)
                        {
                            for (int index=0; index<tobeRemoved.Count; index++)
                            {
                                SIPDialPlan dialPlan = tobeRemoved[index];
                                logger.Debug("Removing expired dial plan for " + dialPlan.Username + ".");
                                m_userDialPlans.Remove(dialPlan.Username);
                                dialPlan = null;
                            }
                        }
                    }*/

                    //m_transactionEngine.PrintPendingTransactions();

                    Thread.Sleep(2000);
                }
            }
            catch (Exception excp)
            {
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, "Exception MeasureObjectUsage. " + excp.Message, null));
            }
        }

        /// <summary>
        /// This event handler is fired when the SIP Registrar wants to send a NATKeepAlive message to a user contact to make an attempt to keep the connection
        /// open through the user's NAT server. In this case the NATKeepAlive messages are forwarded onto the NATKeepAlive relay which requests the SIPTransport
        /// layer to multiplex a 4 byte null payload onto one of its sockets and send to the specified agent. A typical scenario would have the SIP Registrar
        /// firing this event every 15s to send the null payloads to each of its registered contacts.
        /// </summary>
        private void SendNATKeepAlive(NATKeepAliveMessage keepAliveMessage)
        {
            try
            {
                if (m_natKeepAliveEndPoint != null && m_natKeepAliveSender != null)
                {
                    byte[] buffer = keepAliveMessage.ToBuffer();
                    m_natKeepAliveSender.Send(buffer, buffer.Length, m_natKeepAliveEndPoint);
                }
            }
            catch (Exception natSendExcp)
            {
                logger.Warn("Exception SendNATKeepAlive " + keepAliveMessage.SendToSocket + ". " + natSendExcp.Message);
            }
        }

        /// <summary>
        /// Allows the STUN server to utilise the default UDP SIP socket.
        /// </summary>
        /// <param name="dstEndPoint"></param>
        /// <param name="buffer"></param>
        private void STUNSend(IPEndPoint dstEndPoint, byte[] buffer)
        {
            m_sipProxyTransport.Send(m_sipProxyTransport.GetTransportContact(null), dstEndPoint, SIPProtocolsEnum.UDP, buffer);
        }

        private void SIPCDR_CDRUpdated(SIPCDR cdr)
        {
            try
            {
                if (cdr != null && m_dbLogger != null)
                {
                   m_dbLogger.AddCDR(cdr);
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception SIPCDR_CDRUpdated. " + excp.Message);
            }
        }

        private void rubyScriptWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            try
            {
                logger.Debug("Ruby common script file changed, reloading ruby common script.");

                File.Copy(m_rubyScriptCommonPath, m_rubyScriptCommonPath + PROXY_RUNTIMECONFIG_TEMPEXTEN, true);
                StreamReader proxyRuntimeReader = new StreamReader(m_rubyScriptCommonPath + PROXY_RUNTIMECONFIG_TEMPEXTEN);
                m_rubyScriptCommon = proxyRuntimeReader.ReadToEnd();
                proxyRuntimeReader.Close();

                lock (m_userDialPlans)
                {
                    foreach(SIPDialPlan dialPlan in m_userDialPlans.Values)
                    {
                        if (dialPlan.IsScript)
                        {
                            dialPlan.Dirty = true;
                        }
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception rubyScriptWatcher_Changed. " + excp);
            }
        }

        public void Stop()
        {
            try
            {
                DNSManager.Stop();
                SIPDialPlan.StopScriptMonitoring = true;
                m_stop = true;

#if WINDOWS
                if (m_hService != null)
                {
                    m_hService.Close();
                }
#endif

                if (m_secondarySTUNListener != null)
                {
                    m_secondarySTUNListener.Close();
                }

                if (m_stunServer != null)
                {
                    m_stunServer.Stop();
                }

                if (m_natKeepAliveRelay != null)
                {
                    m_natKeepAliveRelay.Shutdown();
                }

                if (m_dbLogger != null)
                {
                    m_dbLogger.Stop();
                }

                // Stop the registration agent and de-register all current contacts.
                if (m_regAgent != null)
                {
                    m_regAgent.Stop();
                }

                if (m_sipRegistrationAgentPersistor != null)
                {
                    m_sipRegistrationAgentPersistor.Stop = true;
                }

                // Stop the registrar persistor threads.
                if (m_sipSwitchRegistrarPersistor != null)
                {
                    m_sipSwitchRegistrarPersistor.Stop = true;
                }

                // Shutdown the SIPTransport layers.
                m_appServerSIPTransport.Shutdown();
                m_registrarSIPTransport.Shutdown();
                m_regAgentSIPTransport.Shutdown();
                m_sipProxyTransport.Shutdown();

                if (m_monitorChannel != null)
                {
                    m_monitorChannel.Close();
                }

                // Shutdown the proxy monitor.
                if (m_proxyMonitor != null)
                {
                    m_proxyMonitor.Stop();
                }

                // Stop the metrics graph agent.
                if (m_metricsGraphAgent != null)
                {
                    m_metricsGraphAgent.Stop();
                }

                logger.Debug("SIPSwitch stopped.");
            }
            catch (Exception excp)
            {
                logger.Error("Exception StatefulDaemon Stop." + excp.Message);
            }
        }

        /// <summary>
        /// A delegated functiona that authenticates connections to the proxy monitor port.
        /// </summary>
        /// <param name="username">The username entered by the user of the telnet connection.</param>
        /// <param name="password">The password entered by the user of the telnet connection.</param>
        /// <returns>True if authenticated, false otherwise.</returns>
        private bool proxyMonitor_ProxyAuthenticate(string username, string password)
        {
            Console.WriteLine("authenticating username=" + username + " and password=" + password + ".");

            if(username == null)
            {
                return false;
            }

            return m_sipSwitchPersistor.ProxyMonitorAuthenticate(username, password);
        }

        /// <summary>
        /// Function delegate that is used by the SIP Proxy core to load dial plans for incoming authenticated calls. It will
        /// check with the dial plan store whether the dial plan has been updated and if not will attempt to load the dial plan
        /// from cache. Otherwise the dial plan will be loaded from persistent storage.
        /// </summary>
        /// <param name="username">The username of the user to load the dial plan for.</param>
        /// <returns>A SIPDialPlan object if successful, null otherwise.</returns>
        private SIPDialPlan LoadUserDialPlan(string sipAccountUsername, string sipAccountDomain)
        {
            try
            {
                DateTime startTime = DateTime.Now;
                string username = GetSIPAccountOwner(sipAccountUsername, sipAccountDomain);

                SIPDialPlan userDialPlan = null;
                // Look up individual users configs!
                if (username == null || username.Trim().Length == 0)
                {
                    StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Error, "No SIP account owner found for " + sipAccountUsername + "@" + sipAccountDomain + ".", null));
                    return null;
                }
                else if (m_userDialPlans.ContainsKey(username) && !m_userDialPlans[username].Dirty)
                {
                    // Check config is current.
                    bool dialPlanUpdated = m_sipSwitchPersistor.HasDialPlanBeenUpdated(username, sipAccountDomain);

                    if (!dialPlanUpdated)
                    {
                        // Dial plan is current use cached.
                        //logger.Debug("Using cached dialplan for " + username + ".");
                        return m_userDialPlans[username];
                    }
                    else
                    {
                        // Replace existing dialplan by loading new one from database.
                        //logger.Debug("Dialplan for " + username + " is out of date, reloading.");

                        if (m_userDialPlans.ContainsKey(username))
                        {
                            lock (m_userDialPlans)
                            {
                                m_userDialPlans[username] = null;   // De-reference the dialplan object so it can be garbage collected.
                                m_userDialPlans.Remove(username);
                            }
                        }

                        userDialPlan = LoadPersistedDialPlan(username);

                        if (userDialPlan != null)
                        {
                            m_userDialPlans.Add(username, userDialPlan);
                        }
                    }
                }
                else
                {
                    // No dial plan exists for user load from DB.
                    //logger.Debug("Loading dialplan for " + username + ".");

                    // Load new config.
                    userDialPlan = LoadPersistedDialPlan(username);

                    if (userDialPlan != null)
                    {
                        if (m_userDialPlans.ContainsKey(username))
                        {
                            lock (m_userDialPlans)
                            {
                                m_userDialPlans[username] = null;   // De-reference the dialplan object so it can be garbage collected.
                                m_userDialPlans.Remove(username);
                            }
                        }
                        else
                        {
                            m_userDialPlans.Add(username, userDialPlan);
                        }
                    }
                }

                return userDialPlan;
            }
            catch (ArgumentException argExcp)
            {
                throw argExcp;
            }
            catch (Exception excp)
            {
                logger.Error("Exception LoadDialPlan. " + excp.Message);
                return null;
            }
        }

        private SIPDialPlan LoadPersistedDialPlan(string username)
        {
            DateTime startTime = DateTime.Now;

            string dialPlanSrc = m_sipSwitchPersistor.LoadDialPlanSource(username);

            if (dialPlanSrc != null && dialPlanSrc.Trim().Length > 0)
            {
                string emailAddress = m_sipSwitchPersistor.GetUserEmailAddress(username);
                List<SIPProvider> sipProviders = m_sipSwitchPersistor.GetSIPProvidersForUser(username);

                SIPDialPlan userDialPlan = new SIPDialPlan(m_appServerSIPTransport, username, emailAddress, dialPlanSrc, sipProviders, m_rubyScriptCommon, m_sipDomains, m_outboundProxySocket);
                userDialPlan.GetSIPAccountBindings += new GetSIPAccountBindingsDelegate(GetSIPAccountBindings);

                TimeSpan loadTime = DateTime.Now.Subtract(startTime);
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.DialPlan, "Dialplan load time " + loadTime.TotalMilliseconds + "ms.", username));

                return userDialPlan;
            }
            else
            {
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.Warn, "Dialplan was empty for " + username + ".", username));
                return null;
            }
        }

        /// <summary>
        /// Looks up an extension for the SIP Proxy core. If found the owner of the extension is returned which allows
        /// the Proxy core to match the extension to the user and process the incoming call accordingly.
        /// </summary>
        /// <param name="user">The user portion of the incoming call request URI.</param>
        /// <param name="domain">The canonical domain matching the host portion of the incoming call request URI.</param>
        /// <returns>The owner of the extension if found, null otherwise.</returns>
        public string GetExtensionOwner(string user, string domain)
        {
            try
            {
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.NewCall, "Looking up extension " + user + "@" + domain + ".", null));

                if (user != null && user.Trim().Length > 0 && domain != null && domain.Trim().Length > 0)
                {
                    return m_sipSwitchPersistor.GetExtensionOwner(user, domain);
                }
                else
                {
                    return null;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetExtensionOwner. " + excp.Message);
                return null;
            }
        }

        /// <summary>
        /// Looks up a SIP Account for the SIP Proxy core. If found the owner of the SIP Account is returned which allows
        /// the Proxy core to lookup the owning user's dial plan.
        /// </summary>
        /// <param name="user">The user portion of the SIP Account.</param>
        /// <param name="domain">The canonical domain matching the host portion of the SIP Account.</param>
        /// <returns>The owner of the SIP Account if found, null otherwise.</returns>
        public string GetSIPAccountOwner(string user, string domain)
        {
            try
            {
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.RegistrarLookup, "Looking up SIP account " + user + "@" + domain + ".", null));

                if (user != null && user.Trim().Length > 0 && domain != null && domain.Trim().Length > 0)
                {
                    return m_sipSwitchPersistor.GetSIPAccountOwner(user, domain);
                }
                else
                {
                    return null;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetSIPAccountOwner. " + excp.Message);
                return null;
            }
        }

        private List<UserRegistration> Load3rdPartyRegistrations(StorageTypes storagetype, string dbConnStr)
        {
            try
            {
                return m_sipSwitchPersistor.Load3rdPartyRegistrations();
            }
            catch (Exception excp)
            {
                logger.Error("Exception Load3rdPartyRegistrations. " + excp.Message);
                return null;
            }
        }

        private SIPAccount LoadSIPAccount(string username, string domain)
        {
            return m_sipSwitchPersistor.LoadSIPAccount(username, domain);
        }

        private Dictionary<string, SIPUserAgentConfiguration> LoadUserAgentConfigs(XmlNode userAgentConfigsNode)
        {
            try
            {
                Dictionary<string, SIPUserAgentConfiguration> userAgentConfigs = new Dictionary<string, SIPUserAgentConfiguration>();

                if (userAgentConfigsNode != null && userAgentConfigsNode.ChildNodes.Count != 0)
                {
                    foreach (XmlNode userAgentNode in userAgentConfigsNode.SelectNodes("useragent"))
                    {
                        if (userAgentNode.InnerText != null && userAgentNode.InnerText.Trim().Length > 0 && !userAgentConfigs.ContainsKey(userAgentNode.InnerText))
                        {
                            int expiry = Convert.ToInt32(userAgentNode.Attributes.GetNamedItem("expiry").Value);
                            bool contactListSupported = (userAgentNode.Attributes.GetNamedItem("contactlists") != null) ? Convert.ToBoolean(userAgentNode.Attributes.GetNamedItem("contactlists").Value) : true;
                            SIPUserAgentConfiguration userAgentConfig = new SIPUserAgentConfiguration(expiry, contactListSupported, userAgentNode.InnerText);
                            userAgentConfigs.Add(userAgentNode.InnerText, userAgentConfig);
                            logger.Debug("Added useragent config, useragent=" + userAgentNode.InnerText + ", expiry=" + expiry + "s, contact lists=" + contactListSupported + ".");
                        }
                    }

                    return userAgentConfigs;
                }
                else
                {
                    logger.Warn("LoadUserAgentConfigs did not load any records as the useragents node was null or empty.");
                    return null;
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception LoadUserAgentConfigs. " + excp.Message);
                return null;
            }
        }

        #region Logging functions.

        private void StatefulProxyLogEvent(ProxyMonitorEvent proxyLogEvent)
        {
            try
            {
                if (proxyLogEvent != null)
                {
                    if (m_monitorChannel != null && proxyLogEvent.EventType != ProxyEventTypesEnum.SIPTransaction)
                    {
                        m_monitorChannel.Send(proxyLogEvent);
                    }

                    if (proxyLogEvent.EventType != ProxyEventTypesEnum.FullSIPTrace && 
                        proxyLogEvent.EventType != ProxyEventTypesEnum.SIPTransaction && 
                        proxyLogEvent.EventType != ProxyEventTypesEnum.Timing &&
                        proxyLogEvent.EventType != ProxyEventTypesEnum.ContactRegisterInProgress &&
                        proxyLogEvent.EventType != ProxyEventTypesEnum.Monitor &&
                        proxyLogEvent.EventType != ProxyEventTypesEnum.UnrecognisedMessage &&
                        proxyLogEvent.EventType != ProxyEventTypesEnum.NATKeepAliveRelay)
                    {
                        logger.Debug(proxyLogEvent.Message);
                    }

                    if (m_dbLogger != null && proxyLogEvent.Username != null &&
                        (proxyLogEvent.EventType == ProxyEventTypesEnum.DialPlan ||
                        //proxyLogEvent.EventType == ProxyEventTypesEnum.ContactRemoval ||
                        //proxyLogEvent.EventType == ProxyEventTypesEnum.ContactRegisterFailed ||
                        proxyLogEvent.EventType == ProxyEventTypesEnum.MWI ||
                        proxyLogEvent.EventType == ProxyEventTypesEnum.Switch ||
                        proxyLogEvent.EventType == ProxyEventTypesEnum.Error)//||
                        //proxyLogEvent.EventType == ProxyEventTypesEnum.BindingExpired)
                        )
                    {
                        m_dbLogger.AddEvent(proxyLogEvent);
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception StatefulProxyLogEvent. " + excp.Message);
            }
        }

        private void LogSIPRequestIn(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPRequest sipRequest)
        {
            string message = "Request Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + "\r\n" + sipRequest.ToString();
            LogSIPMessage(message);
        }

        private void LogSIPRequestOut(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPRequest sipRequest)
        {
            string message = "Request Sent (" + protocol + "): " + localEndPoint + "->" + endPoint + "\r\n" + sipRequest.ToString();
            LogSIPMessage(message);
        }

        private void LogSIPResponseIn(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPResponse sipResponse)
        {
            string message = "Response Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + "\r\n" + sipResponse.ToString();
            LogSIPMessage(message);
        }

        private void LogSIPResponseOut(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPResponse sipResponse)
        {
            string message = "Response Sent (" + protocol + "): " + localEndPoint + "->" + endPoint + "\r\n" + sipResponse.ToString();
            LogSIPMessage(message);
        }

        private void LogSIPProxyRequestIn(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPRequest sipRequest)
        {
            string message = "Proxy Request Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + "\r\n" + sipRequest.ToString();
            LogSIPProxyMessage(message);
        }

        private void LogSIPProxyRequestOut(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPRequest sipRequest)
        {
            string message = "Proxy Request Sent (" + protocol + "): " + localEndPoint + "->" + endPoint + "\r\n" + sipRequest.ToString();
            LogSIPProxyMessage(message);
        }

        private void LogSIPProxyResponseIn(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPResponse sipResponse)
        {
            string message = "Proxy Response Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + "\r\n" + sipResponse.ToString();
            LogSIPProxyMessage(message);
        }

        private void LogSIPProxyResponseOut(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, SIPResponse sipResponse)
        {
            string message = "Proxy Response Sent (" + protocol + "): " + localEndPoint + "->" + endPoint + "\r\n" + sipResponse.ToString();
            LogSIPProxyMessage(message);
        }

        private void LogSIPBadRequestIn(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, string sipMessage, SIPValidationError error)
        {
            string message = "Bad Request Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + ", " + error;
            string fullMessage = "Bad Request Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + ", " + error + "\r\n" + sipMessage;
            LogSIPBadMessage(message, fullMessage);
        }

        private void LogSIPBadResponseIn(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint endPoint, string sipMessage, SIPResponseParserError error)
        {
            string message = "Bad Response Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + ", " + error;
            string fullMessage = "Bad Response Received (" + protocol + "): " + localEndPoint + "<-" + endPoint + ", " + error + "\r\n" + sipMessage;
            LogSIPBadMessage(message, fullMessage);
        }

        private void LogSIPMessage(string message)
        {
            try
            {
                if (m_logAll)
                {
                    logger.Debug(message);
                }

                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.FullSIPTrace, message, null));
            }
            catch (Exception excp)
            {
                logger.Error("Exception LogSIPMessage. " + excp.Message);
            }
        }

        private void LogSIPProxyMessage(string message)
        {
            try
            {
                if (m_logAll)
                {
                    logger.Debug(message);
                }

                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatelessProxy, ProxyEventTypesEnum.FullSIPTrace, message, null));
            }
            catch (Exception excp)
            {
                logger.Error("Exception LogSIPProxyMessage. " + excp.Message);
            }
        }

        private void LogSIPBadMessage(string message, string fullTraceMessage)
        {
            try
            {
                if (m_logAll)
                {
                    //logger.Debug(message);
                    logger.Debug(fullTraceMessage);
                }

                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.BadSIPMessage, message, null));
                StatefulProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.FullSIPTrace, fullTraceMessage, null));
            }
            catch (Exception excp)
            {
                logger.Error("Exception LogSIPBadMessage. " + excp.Message);
            }
        }

        private void LogPrimarySTUNRequestReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, byte[] buffer, int bufferLength)
        {
            ProxyMonitorEvent stunEvent = new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.STUNPrimary, "Primary STUN request received from " + IPSocket.GetSocketString(remoteEndPoint) + ".", null);
            StatefulProxyLogEvent(stunEvent);
        }

        private void LogSecondarySTUNRequestReceived(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, byte[] buffer, int bufferLength)
        {
            ProxyMonitorEvent stunEvent = new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.STUNSecondary, "Secondary STUN request recevied from " + IPSocket.GetSocketString(remoteEndPoint) + ".", null);
            StatefulProxyLogEvent(stunEvent);
        }

        private void UnrecognisedMessageReceived(IPEndPoint localEndPoint, IPEndPoint fromEndPoint, byte[] buffer)
        {
            int bufferLength = (buffer != null) ? buffer.Length : 0;
            string msg = null;

            if (bufferLength > 0)
            {
                if (buffer.Length > 128)
                {
                    msg = " =>" + Encoding.ASCII.GetString(buffer, 0, 128) + "...<=";
                }
                else
                {
                    msg = " =>" + Encoding.ASCII.GetString(buffer) + "<=";
                }
            }

            ProxyMonitorEvent unrecgMsgEvent = new ProxyMonitorEvent(ProxyServerTypesEnum.StatefulProxy, ProxyEventTypesEnum.UnrecognisedMessage, "Unrecognised packet received from " + fromEndPoint + " on " + localEndPoint + ", bytes=" + bufferLength + " " + msg + ".", null);
            StatefulProxyLogEvent(unrecgMsgEvent);
        }

        #endregion
    }
}
